package com.jash.labor.context;

import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.util.WebUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;

/**
 * 主要通途: 快速构建错误信息.
 */
@Component
public class ErrorInfoBuilder {

    private static final ErrorResult API_ERROR = new ErrorResult(1001, "系统错误，请稍后重试！");
    private static final ErrorResult PARAM_REQUIRED = new ErrorResult(1002, "%s 参数必传");
    private static final ErrorResult PARAM_ERROR = new ErrorResult(1003, "%s 参数错误");


    /**
     * 错误配置(ErrorConfiguration)
     */
    private ErrorProperties errorProperties;

    public ErrorProperties getErrorProperties() {
        return errorProperties;
    }

    public void setErrorProperties(ErrorProperties errorProperties) {
        this.errorProperties = errorProperties;
    }

    /**
     * 错误构造器 (Constructor) 传递配置属性：server.xx -> server.error.xx
     */
    public ErrorInfoBuilder(ServerProperties serverProperties) {
        this.errorProperties = serverProperties.getError();
    }

    /**
     * 构建错误信息.
     */
    public Result getErrorInfo(HttpServletRequest request) {

        return getErrorInfo(request, getError(request));
    }

    public Result getErrorInfo(HttpServletRequest request, Throwable error) {
        Result result = new Result();
        ErrorResult errorResult;
        //自定义异常处理
        if (error instanceof ApiException) {

            errorResult = ((ApiException) error).getResultCode();

        } else if (error instanceof MissingServletRequestParameterException) {

            errorResult = PARAM_REQUIRED;
            errorResult.setError(String.format(errorResult.getError(), ((MissingServletRequestParameterException) error).getParameterName()));

        } else if (error instanceof MethodArgumentTypeMismatchException) {

            errorResult = PARAM_ERROR;
            errorResult.setError(String.format(errorResult.getError(), ((MethodArgumentTypeMismatchException) error).getName()));

        } else if (getHttpStatus(request).value() == 404) {

            errorResult = new ErrorResult(404, "[" + request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE) + "] " + getHttpStatus(request).getReasonPhrase());

        } else {

            errorResult = API_ERROR;

        }
        result.setCode(errorResult.getErrorCode());
        result.setMsg(errorResult.getError());
        result.setData(Collections.emptyMap());
        return result;
    }

    /**
     * 获取错误.(Error/Exception)
     * <p>
     * 获取方式：通过Request对象获取(Key="javax.servlet.error.exception").
     *
     * @see DefaultErrorAttributes #addErrorDetails
     */
    public Throwable getError(HttpServletRequest request) {
        //根据Request对象获取错误.
        Throwable error = (Throwable) request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE);
        //当获取错误非空,取出RootCause.
        if (error != null) {
            while (error instanceof ServletException && error.getCause() != null) {
                error = error.getCause();
            }
        }//当获取错误为null,此时我们设置错误信息即可.
        else {
            String message = (String) request.getAttribute(WebUtils.ERROR_MESSAGE_ATTRIBUTE);
            if (StringUtils.isEmpty(message)) {
                HttpStatus status = getHttpStatus(request);
                message = "Unknown Exception With " + status.value() + " " + status.getReasonPhrase();
            }
            error = new Exception(message);
        }
        return error;
    }

    /**
     * 获取通信状态(HttpStatus)
     *
     * @see AbstractErrorController #getStatus
     */
    public HttpStatus getHttpStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE);
        try {
            return statusCode != null ? HttpStatus.valueOf(statusCode) : HttpStatus.INTERNAL_SERVER_ERROR;
        } catch (Exception ex) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
    }

}
